package com.yuan.yblog.portal.service;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yuan.yblog.common.consts.RedisKey;
import com.yuan.yblog.common.service.RedisService;
import com.yuan.yblog.common.utils.StringUtils;
import com.yuan.yblog.crud.service.IPostService;
import com.yuan.yblog.portal.pojo.VO.PostRankVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

import com.yuan.yblog.crud.entity.Post;

@Service
public class WeekRankService {

    @Autowired
    IPostService iPostService;

    @Autowired
    RedisService redisService;


    /**
     * 近7天的文章（后备榜）
     */
    public static final String RANK_POST = "rank:post:";

    /**
     * 文章周榜
     */
    public static final String RANK_WEEK = "rank:week";

    /**
     * true表示当前文章周榜是最新的
     */
    private AtomicBoolean isUpdated = new AtomicBoolean(false);

    /**
     * 文章周榜的hashKey，表示【文章id】
     */
    private static final String HASH_KEY_POST_ID = "postId";
    /**
     * 文章周榜的hashKey，表示【文章标题】
     */
    private static final String HASH_KEY_TITLE = "title";

    /**
     * 文章周榜的hashKey，表示【文章评论数】
     */
    private static final String HASH_KEY_COMMENT_COUNT = "commentCount";

    /**
     * 文章周榜的hashKey，表示【文章阅读数】
     */
    private static final String HASH_KEY_VIEW_COUNT = "viewCount";

    /**
     * 文章周榜的hashKey，表示【文章点赞数】
     */
    private static final String HASH_KEY_VOTE_UP = "voteUp";

    /**
     * 文章周榜的hashKey，表示【文章创建日期】
     */
    private static final String HASH_KEY_CREATED = "created";

    /**
     * 文章周榜更新类型
     */
    public enum UpdateType {
        COMMENT,
        VIEW,
        VOTE,
    }

    /**
     * 查询周榜是否是最新的
     * @return true表示是最新的
     */
    public boolean isNewest() {
        if(this.isUpdated == null) {
            return false;
        }
        return this.isUpdated.get();
    }

    /**
     * 初始化：将过去一周文章查询出来，存到周榜【后备榜】，然后做并集计算权重后，得到文章周榜
     */
    public void initWeekRank() {
        // 查询过去一周的文章
        List<Post> postList = iPostService.getBaseMapper().selectList(new QueryWrapper<Post>()
                .ge("created", DateUtil.offsetDay(new Date(),-7))/*查询七天内的文章*/
                .orderByDesc("comment_count","view_count","vote_up")/*排序返回*/
                .last("limit 10") /*只查询前10条*/
                .select("id,title,comment_count,view_count,created,vote_up"));/*查询指定字段*/
        // 将过去一周的文章按创建日期，存入redis
        for(Post post : postList){
            insert(post);
        }
        // 做并集
        this.zunionAndStoreLast7DayForWeekRank();
    }

    /**
     * 获取当前文章周榜
     * @return 文章周榜列表
     */
    public List<PostRankVO> getRankList() {
        //获取集合
        String key = RANK_WEEK;
        Long size = redisService.zSetGetCount(key);
        Set<Object> list = redisService.zGetReverse(key,0L,size-1);
        //集合对象转化
        List<PostRankVO> result = new ArrayList<>();
        for(Object o : list){
            result.add(JSON.parseObject(JSON.toJSONString(o), PostRankVO.class));
        }
        return result;
    }

    /**
     * 更新周榜文章数据
     * @param postId 文章id
     * @param offset 偏移值，正表示增加，负表示减小
     */
    public void updatePost(long postId, long offset, UpdateType updateType) {
        String key = RANK_WEEK + postId;
        if(redisService.hasKey(key)){
            Long value = (Long) redisService.hGet(key,HASH_KEY_COMMENT_COUNT);
            // 选择更新的hash-key
            String hashKey = null;
            switch (updateType) {
                case VIEW -> hashKey = HASH_KEY_VIEW_COUNT;
                case COMMENT -> hashKey = HASH_KEY_COMMENT_COUNT;
                case VOTE -> hashKey = HASH_KEY_VOTE_UP;
            }
            // 操作更新
            if (value != null && !StringUtils.isEmpty(hashKey)) {
                redisService.hSet(key, hashKey, value + offset);
                rankChanged();
            }

        }
    }

    /**
     * 将文章插入排行榜
     * @param post 文章实体
     */
    public void insertPost(Post post) {
        DateUtil.between(new Date(), post.getCreated(), DateUnit.DAY, false);
        // 【当前时间】与【文章创建时间】的差值，差值=0~7表示为过去七天内发布的文章
        long dayOffset = DateUtil.between(post.getCreated(), new Date(), DateUnit.DAY, false);
        if (dayOffset <= 7 && dayOffset >= 0) {
            insert(post);
            rankChanged();
        }
    }

    public static void main(String[] args) {
        System.out.println(DateUtil.between(new Date(), new Date(1716219707000L), DateUnit.DAY, false));
    }

    /**
     * 将文章从排行榜删除
     * @param postId 文章id
     */
    public void deletePost(Long postId) {
        String key = RANK_POST + postId;
        if(redisService.hasKey(key)) {
            redisService.del(key);
            rankChanged();
        }
    }

    /**
     * 将文章插入排行榜（redis）
     * @param post
     */
    private void insert(Post post){
        if(post == null
                || post.getId() == null
                || post.getTitle() == null
                || post.getCommentCount() == null
                || post.getViewCount() == null
                || post.getVoteUp() == null
                || post.getCreated() == null) {
            return;
        }
        String key = RANK_POST + post.getId();
        //自文章创建之日起，七天后自动过期，单位：秒
        long keyExpire = (7 - (DateUtil.between(new Date(), post.getCreated(), DateUnit.DAY)))*24*60*60;
        redisService.hSet(key,HASH_KEY_POST_ID,post.getId(),keyExpire);
        redisService.hSet(key,HASH_KEY_TITLE,post.getTitle(),keyExpire);
        redisService.hSet(key,HASH_KEY_COMMENT_COUNT,post.getCommentCount(),keyExpire);
        redisService.hSet(key,HASH_KEY_VIEW_COUNT,post.getViewCount(),keyExpire);
        redisService.hSet(key,HASH_KEY_VOTE_UP,post.getVoteUp(),keyExpire);
        redisService.hSet(key,HASH_KEY_CREATED,post.getCreated().getTime(),keyExpire);//转为时间戳
    }

    /**
     * 周榜已更新
     */
    synchronized private void updateSuccessfully(){
        this.isUpdated.compareAndSet(false, true);
    }

    /**
     * 排行榜发生变化，周榜未更新
     */
    synchronized private void rankChanged(){
        this.isUpdated.compareAndSet(true, false);
    }

    /**
     * 对七天文章天榜做并集，得出周榜
     */
    public void rankUnion(){
        zunionAndStoreLast7DayForWeekRank();
    }

    /**
     * 将前七天所有文章的相关数据做并集，得到排行权重值后，排序，获取考前的文章存入周榜
     */
    private void zunionAndStoreLast7DayForWeekRank(){
        //获取前七天上榜的文章
        List<PostRankVO> postList = new ArrayList<>();
        Set<String> keys = redisService.getKeys(RANK_POST +"*");
        for(String key : keys){
            //权重计算：评论数*3 + 点赞数*2 + 观看数*1
            PostRankVO postRankVO = new PostRankVO();
            postRankVO.setPostId(Long.parseLong(String.valueOf( redisService.hGet(key,HASH_KEY_POST_ID) )));
            postRankVO.setTitle(String.valueOf(redisService.hGet(key, HASH_KEY_TITLE) ));
            postRankVO.setCommentCount(Long.parseLong(String.valueOf(redisService.hGet(key, HASH_KEY_COMMENT_COUNT) )));
            postRankVO.setViewCount(Long.parseLong(String.valueOf( redisService.hGet(key,HASH_KEY_VIEW_COUNT) )));
            postRankVO.setVoteUp(Long.parseLong(String.valueOf( redisService.hGet(key,HASH_KEY_VOTE_UP) )));
            postRankVO.setPostCreated(new Date(Long.parseLong(String.valueOf( redisService.hGet(key,HASH_KEY_CREATED) ))));
            postRankVO.setWeight( postRankVO.getCommentCount()*3 + postRankVO.getVoteUp()*2 + postRankVO.getVoteUp() );
            postList.add(postRankVO);
        }

        //文章排序：HashMap若要根据value来排序，要转成list。
        Collections.sort(postList, new Comparator<PostRankVO>() {
            public int compare(PostRankVO p1, PostRankVO p2) {
                return (p2.getWeight()).compareTo(p1.getWeight());
            }
        });

        //写入周榜
        int count = 0;
        for(PostRankVO postRankVO : postList){
            redisService.zSet(RANK_WEEK, postRankVO, postRankVO.getWeight());
            if((count++)>10) {
                //只排行前10名
                break;
            }
        }
        updateSuccessfully();
    }

}
